package ru.ifmo.ctd.intsys.afanasyeva.neural;

import org.encog.engine.network.activation.ActivationSigmoid;
import org.encog.ml.data.MLData;
import org.encog.ml.data.MLDataSet;
import org.encog.ml.data.basic.BasicMLData;
import org.encog.ml.data.basic.BasicMLDataSet;
import org.encog.ml.train.MLTrain;
import org.encog.neural.networks.BasicNetwork;
import org.encog.neural.networks.layers.BasicLayer;
import org.encog.neural.networks.training.propagation.quick.QuickPropagation;

/**
 * Wrapper of the {@link BasicNetwork} from the Encog-library.
 * 
 * @author Arina Afanasyeva
 */
public class EncogBasicNetwork implements NeuralNetwork {
	private BasicNetwork network;	
	private double error; 
	
	/**
	 * Constructs {@link EncogBasicNetwork} with the specified structure.
	 * @param error error percent the trained network may produce
	 * @param structure number of neurons in each layer
	 */
	public EncogBasicNetwork(double error, int... structure) {
		this.error = error;
		
		int len = structure.length;
		this.network = new BasicNetwork();
		network.addLayer(new BasicLayer(null, true, structure[0]));		
		for (int i = 1; i < len - 1; i++) {
			network.addLayer(new BasicLayer(new ActivationSigmoid(),true, structure[i]));
		}		
		network.addLayer(new BasicLayer(new ActivationSigmoid(),false,structure[len - 1]));
		
		network.getStructure().finalizeStructure();
		network.reset();
	}
	
	/**
	 * Constructs {@link EncogBasicNetwork}, which wraps the specified network
	 * @param network the specified network
	 */
	public EncogBasicNetwork(BasicNetwork network, double error) {
		this.error = error;
		this.network = network;
	}
	
	/**
	 * Gets the {@link BasicNetwork} wrapped by this class.
	 * @return the wrapped network
	 */
	public BasicNetwork getNetwork() {
		return network;
	}
	
	/**
	 * Sets the error percent the trained network may produce.
	 * Reaching this value is the condition of stopping the training.
	 * @param error error percent the trained network may produce
	 */
	public void setError(double error) {
		this.error = error;
	}
	
	@Override
	public double[] getOutput(double[] input) {
		MLData inputData = new BasicMLData(input);
		return network.compute(inputData).getData();
	}

	@Override
	public void train(double[][] inputs, double[][] answers) {
		MLDataSet trainingSet = new BasicMLDataSet(inputs, answers);

		final MLTrain train = new QuickPropagation(network, trainingSet);

		do {
			train.iteration();
		} while(train.getError() > error);
	}

	@Override
	public void reset() {
		network.reset();
	}

}
